#include "ls_ota.h"
#include "ls_common.h"
#include "ls_trace_common.h"
#include "ls_partition.h"
#include "ls_struct_flash.h"
// #include "ls_dm.h"


/// @brief 
typedef struct lsOTA_HEADER_S{
    union {
        #pragma pack(push, 1)
        struct {
            uint32_t magic;
            uint32_t len;
            char  device[32];
            char  name[32];
            char  ver_software[32];
            char  ver_hardware[32];
            char  sha256[32];
        };
        #pragma pack(pop)
        uint8_t _pad[512];
    };
}OTA_HEADER_S;


#define OTA_FLASH_NAME_FW1  "FW1"
#define OTA_FLASH_NAME_FW2  "FW2"

#define OTA_TECV_BUFF_SIZE (512)
//ATTR_PSRAM_SECTION
static  ATTR_NOINIT_PSRAM_SECTION char www_buff[32];
static  char *recv_buff;
static  char *send_buff;
static ATTR_NOCACHE_RAM_SECTION struct bflb_sha256_ctx_s ctx_sha256;
static struct sockaddr_in remote_addr;
static pthread_t  ota_pid = NULL;


#define LS_OTA_GET_FIX  \
"GET /%s HTTP/1.1\r\n" \
"Accept: */*\r\n" \
"User-Agent: LS/1.0\r\n" \
"Host: %s:%d\r\n" \
"Connection: close\r\n" \
"\r\n"


void ls_ota_realse();


/// @brief 
/// @param url 
/// @param ipv4 
/// @param filename 
/// @return 
int32_t url_parse(const char *url,struct sockaddr_in *ipv4,char *_www_buff,char *filename)
{
    int status = -1;
    int offset = strlen("http://");
    if(strncmp(url,"http://",offset) != 0)
        return status;

    char *url_copy = strdup(url);
    char *protocol=NULL;
    char *host=NULL;
    char *port=NULL;
    char *file=NULL;
    
    protocol=strtok(url_copy,":");
    
    uint8_t need_port = 1;
    if(strstr(url_copy+offset,":"))
    {
        host = strtok(NULL, ":");  //分割出主机部分
    }
    else
    {
        need_port = 0;
        host = strtok(NULL, "/");  //分割出主机部分
    }

    LS_TRACE_DEBUG("host:%x url_copy:%x",host,url_copy);
    if(!host)
    {
        LS_TRACE_ERROR("url host error: %s",url);
        free(url_copy);
        return status;
    }
    if(host[0] == '/')
        host+=2;
    

    if(need_port)
    port = strtok(NULL, "/");  // 分割端口号部分

    if(!port)
    {
        port = "80";
    }

    //
    LS_TRACE_DEBUG("protocol:%s",protocol);
    LS_TRACE_DEBUG("host:%s",host);
    LS_TRACE_DEBUG("port:%s",port);

    file = strtok(NULL,'\0');   //
    if(!file)
    {
        LS_TRACE_ERROR("url filename error: %s",url);
        free(url_copy);
        return status;
    }


    LS_TRACE_DEBUG("filename:%s",file);



    int err_cnt = 2;
    struct hostent* hostinfo ;
    do
    {
        hostinfo= gethostbyname(host);
        if(hostinfo != NULL)
            break;
        LS_TRACE_INFO("err_cnt:%d",err_cnt);
        sleep(1);
    }while (--err_cnt);
    
    if (!hostinfo || err_cnt  == 0) {
        LS_TRACE_ERROR("hostinfo error: %s",url);
        free(url_copy);
        return status;
    }

    ipv4->sin_family = AF_INET;
    ipv4->sin_port   = htons(atoi(port));
    memcpy(&ipv4->sin_addr,hostinfo->h_addr_list[0],sizeof(struct sockaddr_in));
    status =0;

    LS_TRACE_DEBUG("name: %s", hostinfo->h_name);
    LS_TRACE_DEBUG("ip:%s",inet_ntoa(ipv4->sin_addr));

    //
    strcpy(_www_buff,host);
    strcpy(filename,file);
    //
    free(url_copy);

    return status;
}


/// @brief 
/// @param buffer 
/// @param filelen 
/// @return 
char *http_respone_parse(const char *buffer,int32_t *code,int32_t *filelen)
{
    int response_code = -1;
    int content_length = -1;
    LS_TRACE_DEBUG("buffer:\r\n%s",buffer);
    char* content_http_header = strstr(buffer, "HTTP/1.1");
 
    if(content_http_header){

        sscanf(content_http_header,"HTTP/1.1 %d ", &response_code);
    }
    else
    {
        content_http_header = strstr(buffer, "HTTP/1.0");
        if(content_http_header){
            sscanf(content_http_header, "HTTP/1.0 %d", &response_code);
        }
    }

    char* content_length_header = strstr(buffer, "Content-Length:");
    if (content_length_header) {
        sscanf(content_length_header, "Content-Length: %d", &content_length);
    }

    
    *filelen = content_length;
    *code    = response_code;
       
    char *lastcontent = NULL;
    lastcontent = strstr(buffer,"\r\n\r\n");
    
    if(lastcontent)
    {
        lastcontent +=4;
    }

    return lastcontent;
}



/// @brief 
/// @param ota_header 
/// @param ota_len 
/// @param use_xz 
/// @return 
static int _ls_check_ota_header(OTA_HEADER_S *ota_header)
{
#define MAGIC_CODE     0x54504642
    char str[33]; //assume max segment size
    int i;

    LS_TRACE_DEBUG("%X %X %X %X",ota_header->_pad[0],ota_header->_pad[1],ota_header->_pad[2],ota_header->_pad[3]);
    LS_TRACE_DEBUG("ota_header->magic:%X",ota_header->magic);
    if(ota_header->magic != MAGIC_CODE) return 1;

    memset(str,0,sizeof(str));
    strcpy(str,ota_header->device);
 
    LS_TRACE_DEBUG("ota header is %s", str);

    memset(str,0,sizeof(str));
    strcpy(str, ota_header->name);
    LS_TRACE_DEBUG("file type is %s", str);

    memset(str,0,sizeof(str));
    strcpy(str, ota_header->ver_hardware);
    LS_TRACE_DEBUG("ver_hardware is %s", str);

    memset(str,0,sizeof(str));
    strcpy(str, ota_header->ver_software);
    LS_TRACE_DEBUG("ver_software is %s",str);

    LS_TRACE_DEBUG("file length is %lu",ota_header->len);
    LS_TRACE_DEBUG("sha256 ");
    for (int i = 0; i < sizeof(ota_header->sha256); i++) {
        printf("%02x", ota_header->sha256[i]);
    }
    printf("\r\n");

    return 0;
}



static void rebootCallback( TimerHandle_t xTimer )
{
    //
    vTaskDelay(1000);
    GLB_SW_POR_Reset();
    vTaskDelay(10);
}

/// @brief 
/// @param p 
/// @return 
void *__ls_ota_http_task(void *p)
{
    int sock_client = -1;
    struct sockaddr_in *ipv4=(struct sockaddr_in *)p;
    uint8_t err_cnt = 0;
    memset(send_buff,0,strlen(send_buff));
    snprintf(send_buff,OTA_TECV_BUFF_SIZE,LS_OTA_GET_FIX,recv_buff,www_buff,htons(ipv4->sin_port));
    
    //分区表的获取
    pt_table_stuff_config table_stuff;
    pt_table_entry_config pt_fw_entry;
    const char *ota_partition_name = OTA_FLASH_NAME_FW2;
    pt_table_error_type   perr = pt_table_get_active_partition_info(&table_stuff);
    if(perr != PT_ERROR_SUCCESS)
    {
        LS_TRACE_ERROR("get partition error!");
        goto GOTO_OTA_ERROR_END;
    }


    // char runName[32];
    // ls_struct_getMainName(runName);

    // 
    // if(strncmp(runName,OTA_FLASH_NAME_FW2,strlen(OTA_FLASH_NAME_FW2)) == 0)
    // {
    //     ota_partition_name = OTA_FLASH_NAME_FW1;
    // };

 
    
    // if (pt_table_get_active_entries_by_name(&table_stuff, (uint8_t *)ota_partition_name,&pt_fw_entry)){
    //     LS_TRACE_ERROR("get active entries by name fail!");
    //     goto GOTO_OTA_ERROR_END;
    // }

    uint32_t ota_flash_addr = 0;
    uint32_t partition_size = 0;
    uint32_t bin_size = 0x400000;
    int32_t  status   = -1;

    //sha
    struct bflb_device_s *sha;
    sha = bflb_device_get_by_name("sha");
    bflb_group0_request_sha_access(sha);
    bflb_sha_init(sha, SHA_MODE_SHA256);
    bflb_sha256_start(sha, &ctx_sha256);

    
OTA_HTTP_RTRY:
    //
    if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        LS_TRACE_ERROR("Http Client create socket error");
        goto GOTO_OTA_ERROR_END;
    }

    //
    if (connect(sock_client, (struct sockaddr *)ipv4, sizeof(struct sockaddr)) != 0) {
        LS_TRACE_ERROR("Http client connect server falied!");
        closesocket(sock_client);
        goto GOTO_OTA_ERROR_END;
    }

    LS_TRACE_DEBUG("Http client connect server success!");
    LS_TRACE_DEBUG("send:\r\n%s",send_buff);
    write(sock_client,send_buff,strlen(send_buff));


    uint8_t  sha256_img[32];
    uint8_t  sha256_result[32];

    uint32_t total_cnt = 0;
    uint32_t flash_write_offset = 0;
    int16_t  count = 0;
    int16_t  recv_buff_offset = 0;
    uint8_t  http_header = 0;
    uint8_t  ota_header_found = 0;

    memset(recv_buff,0,strlen(recv_buff));
    while (1){
        count = recv(sock_client, (uint8_t *)recv_buff+recv_buff_offset,OTA_TECV_BUFF_SIZE-recv_buff_offset, 0);
        if(count <= 0 &&  total_cnt == 0)
        {
            err_cnt++;
            closesocket(sock_client);
            sock_client = -1;
            vTaskDelay(1000);
            LS_TRACE_DEBUG("try again res=%d",err_cnt);
            if(err_cnt < 5)
                goto OTA_HTTP_RTRY;
            else
            {
                LS_TRACE_DEBUG("http request error");
                break;
            }
                
        }
        else if(count <= 0)
        {
            LS_TRACE_DEBUG("Get success total_cnt=%d",total_cnt);
            break;
        }
        else
        {
            int32_t respone_code;
            int32_t content_length;
            char *conten = recv_buff;
            //检查HTTP的头
            if(!http_header)
            {
               conten = http_respone_parse(recv_buff,&respone_code,&content_length);
               if(!conten)
               {
                 recv_buff_offset = strlen(recv_buff);
                 continue;
               }

               if(respone_code != 200)
               {
                  LS_TRACE_ERROR("respone_code %d",respone_code);
                  continue;
               } 
               int16_t header_len =conten - recv_buff;
               
               LS_TRACE_DEBUG("header len:%d",header_len);
               LS_TRACE_DEBUG("content_length:%d",content_length); 
               
               http_header = 1;
               total_cnt = count-header_len;
               recv_buff_offset = total_cnt; 
               LS_TRACE_DEBUG("total_cnt:%d",total_cnt);
               if(total_cnt)
               {
                  memcpy(recv_buff,recv_buff+header_len,recv_buff_offset);
               }
               ls_dm_send_update(0,bin_size);
               ls_dm_send_update(0,bin_size);
               continue;
            }
            else
            {
                total_cnt+=count;
                recv_buff_offset += count;
            }


            //检查OTA的头
            if(!ota_header_found)
            {
               if(total_cnt < 512)
               {
                    recv_buff_offset = total_cnt;
                    continue; 
               }

               OTA_HEADER_S *stOTAHeader =(OTA_HEADER_S *)conten;
               if (_ls_check_ota_header(stOTAHeader) || stOTAHeader->len >= 4194304) {
                    LS_TRACE_ERROR("ota header error!!!!");
                    break;
               }
               bin_size = stOTAHeader->len;
               ota_header_found = 1;
               recv_buff_offset = 0;
               total_cnt       -= sizeof(OTA_HEADER_S);
               memcpy(sha256_img,stOTAHeader->sha256, sizeof(sha256_img));
               
                //升级分区的名字,否则其他分区
                if(strstr(stOTAHeader->name,"FW"))
                {
                    char runName[32];
                    ls_struct_getMainName(runName);
                    LS_TRACE_ERROR("runName:%s!!!!",runName);
                    ota_partition_name = OTA_FLASH_NAME_FW2;
                    if(strncmp(runName,OTA_FLASH_NAME_FW2,strlen(OTA_FLASH_NAME_FW2)) == 0)
                    {
                        ota_partition_name = OTA_FLASH_NAME_FW1;
                    };

                    //
                    if (pt_table_get_active_entries_by_name(&table_stuff, (uint8_t *)ota_partition_name,&pt_fw_entry)){
                        LS_TRACE_ERROR("get active entries by name fail!");
                        break;
                    }

                    ota_flash_addr = pt_fw_entry.start_address[0];
                    partition_size= pt_fw_entry.max_len[0];
                }
                else
                {
                    //
                    strcpy(ota_partition_name,stOTAHeader->name);
                    if (pt_table_get_active_entries_by_name(&table_stuff, (uint8_t *)ota_partition_name,&pt_fw_entry)){
                        LS_TRACE_ERROR("get active entries by name fail!");
                        break;
                    }

                    ota_flash_addr = pt_fw_entry.start_address[0];
                    partition_size= pt_fw_entry.max_len[0];    
                }
                
                LS_TRACE_DEBUG("Update bin_size %s to %lu",ota_partition_name, bin_size);
                //
                if(bin_size > partition_size)
                {
                    LS_TRACE_ERROR("partition error break ota!");
                    break;
                }

                LS_TRACE_ERROR("ota_flash_addr:%p partition_size:%p ota_partition_name:%s",ota_flash_addr,partition_size,ota_partition_name);
                status = bflb_flash_erase(ota_flash_addr,partition_size); /* erase flash */
                if (status != 0){
                    LS_TRACE_ERROR("flash erase fail! %d", status);
                    break;
                }

               continue;
            }

            
            if(bin_size != total_cnt)
            {   
                if(recv_buff_offset < OTA_TECV_BUFF_SIZE)continue;
                else if (recv_buff_offset > OTA_TECV_BUFF_SIZE)
                {
                    LS_TRACE_DEBUG("recv for unexpected error %d",recv_buff_offset);    
                    break;
                }
            }
            else if (total_cnt > bin_size)
            {
                LS_TRACE_DEBUG("recv for unexpected error %d ? %d",total_cnt,bin_size);    
                break;
            }
            
            //sha 256 解密
            bflb_l1c_dcache_clean_range(recv_buff,OTA_TECV_BUFF_SIZE);

            status = bflb_sha256_update(sha,&ctx_sha256,recv_buff,recv_buff_offset);
            if (status != 0) {
                LS_TRACE_DEBUG("sha256 update fail! %d", status);
                break;
            }

            //
            status = bflb_flash_write((ota_flash_addr + flash_write_offset),recv_buff,recv_buff_offset);
            if (status != 0) {
                LS_TRACE_DEBUG("flash write fail! %d", status);
                break;
            }
            
            flash_write_offset += recv_buff_offset;
            recv_buff_offset = 0;

            printf("-------rate:%d/%d-------",total_cnt,bin_size);
            printf("\r");  
            //表示下载到falsh完成
            if (bin_size == total_cnt)
            {   
                ls_dm_send_update(total_cnt,total_cnt);
                ls_dm_send_update(total_cnt,total_cnt);
                printf("\r\n");
                bflb_sha256_finish(sha, &ctx_sha256, sha256_result);
                LS_TRACE_DEBUG("sha256_result: ");
                for (int i = 0; i < sizeof(sha256_result); i++) {
                    printf("%02x", sha256_result[i]);
                }
                printf("\r\n");

                if(memcmp(sha256_img, sha256_result, sizeof(sha256_img))){
                    /*Error found*/
                    LS_TRACE_ERROR("SHA256:");
                    printf("result:%.X",sizeof(sha256_result),sha256_result);
                    printf("img:%.X",sizeof(sha256_img),sha256_img);
                    LS_TRACE_ERROR("SHA256 NOT Correct:%d",total_cnt);
                    break;
                }

                //
                pt_fw_entry.active_index = 0;
                pt_fw_entry.len = bin_size;
                pt_fw_entry.age++;
                status = pt_table_update_entry(&table_stuff,&pt_fw_entry);
                if (status != 0) {
                    LS_TRACE_ERROR("pt table update fail! %d",status);
                    break;
                }

                //
                ls_struct_setOTAName(ota_partition_name);

                //
                LS_TRACE_DEBUG("OTA SUCCESS rebooting!");
                //
                TimerHandle_t timeId = xTimerCreate("reboot",3000,pdFALSE,(void*)1,rebootCallback);
                xTimerStart(timeId,1000);

            }
            
            // LS_TRACE_DEBUG("total_cnt:%d",total_cnt);
        }

    }

GOTO_OTA_ERROR_END:
    //
    if(sock_client != -1)
    {
       LS_TRACE_DEBUG("closesocket:%d",sock_client);
       closesocket(sock_client);
       sock_client = -1;
    }
    
    LS_TRACE_DEBUG("ota test end!");
    ls_ota_realse();
    vTaskDelete(NULL);
}







/// @brief 
void ls_ota_start(const char *url)
{
    LS_RETURN_NOT(ota_pid == NULL,"ota is runing");
    send_buff = malloc(OTA_TECV_BUFF_SIZE);
    recv_buff = malloc(OTA_TECV_BUFF_SIZE);
    //
    int s32ret=url_parse(url,&remote_addr,www_buff,recv_buff);
    LS_RETURN_NOT(s32ret == 0,"url parse error");
    //
    pthread_create(&ota_pid,NULL,__ls_ota_http_task,&remote_addr);
}



///
void ls_ota_realse()
{
    LS_RETURN_NOT(ota_pid != NULL,"ota is stop");
    free(send_buff);
    free(recv_buff);
    ota_pid = NULL;
}




#define PING_USAGE                   \
    "http [hostname]/[filename]\r\n" \

static int cmd_ls_ota(int argc, char **argv)
{
    if (argc < 2) {
        printf("%s",PING_USAGE);
        return;
    }
    ls_ota_start(argv[1]);
    return 0;
}
SHELL_CMD_EXPORT_ALIAS(cmd_ls_ota, ls_ota,http ota test);
